﻿using System;
using System.IO;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;


namespace Version3.SharePoint
{
    //This is our value parser, we actually store it in a two column
    //SPFieldMultiColumnValue object, the first column stores
    //the object type name and is used in the column or
    //form value display fields, the second column is the
    //Base64 encoded value of the serialized object

    [Guid("5DF861F5-1415-42c6-AD60-8DCF1E8EB1F5")]
    [CLSCompliant(false)]
    public class FreezeDriedDataFieldValue : IDisposable
    {

        
        //Use a class here instead of an enum to avoid 
        //having to cast the enumerations makes it read
        //a bit better 
        private class ColumnUsage
        {
           public const int DisplayText = 0;
           public const int DataType = 1;
           public const int Reserved = 2;
           public const int FreezeDriedData = 3;

           public const int COLUMN_ARRAY_SIZE = 4;
           public const string COLUMN_DELIMITER = ";#";
        }

        private SPFieldMultiColumnValue _ColumnData = null;
        private object _RehydratedData = null;
        private Exception _LastError = null;

        public FreezeDriedDataFieldValue()
            :this(string.Empty)
        {
        }

        public FreezeDriedDataFieldValue(object fieldData)
        {
            ParseValue(fieldData,string.Empty);
        }

        public FreezeDriedDataFieldValue(string fieldData)
        {
            ParseValue(fieldData,string.Empty);
        }

        public FreezeDriedDataFieldValue(object fieldData, string columnDisplayText)
        {
            ParseValue(fieldData,columnDisplayText);
        }

        public FreezeDriedDataFieldValue(string fieldData, string columnDisplayText)
        {
            ParseValue(fieldData,columnDisplayText);
        }

        public object Value
        {
            get
            {
                if (null == _RehydratedData)
                {
                    _RehydratedData = HydrateObjectFromBase64String(_ColumnData[ColumnUsage.FreezeDriedData], ref _LastError);
                }
                return _RehydratedData;
            }
            set
            {
                ParseValue(value,this.DisplayText);
            }
        }

        public Exception LastError
        {
            get
            {
                return _LastError;
            }
        }

        public string DataTypeName
        {
            get
            {
                return _ColumnData[ColumnUsage.DataType];
            }
        }

        public string DisplayText
        {
            get
            {
                return _ColumnData[ColumnUsage.DisplayText];
            }
            set
            {
                _ColumnData[ColumnUsage.DisplayText] = value;
            }
        }

        public string AssemblyHint
        {
            get
            {
                return _ColumnData[ColumnUsage.Reserved];
            }
            set
            {
                _ColumnData[ColumnUsage.Reserved] = value;
            }
        }


        public string FreezeDriedData
        {
            get
            {
                return _ColumnData[ColumnUsage.FreezeDriedData];
            }
        }

        virtual public string ToHTMLString()
        {
            return SPEncode.HtmlEncode(this.DisplayText);
        }

        public override string ToString()
        {
            return _ColumnData.ToString();
        }

        public override int GetHashCode()
        {
            return this.FreezeDriedData.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            return this.Value.Equals(obj);
        }

        internal void ParseValue(object fieldData, string columnDisplayText)
        {
            this.Dispose();
            if (null == fieldData)
            {
                ParseValue(string.Empty,string.Empty);
            }
            else
            {
                if (fieldData.GetType() == typeof(Version3.SharePoint.FreezeDriedDataFieldValue))
                {
                    _ColumnData = new SPFieldMultiColumnValue(((Version3.SharePoint.FreezeDriedDataFieldValue)fieldData).ToString());
                }
                else
                {
                    _ColumnData = new SPFieldMultiColumnValue(ColumnUsage.COLUMN_ARRAY_SIZE);
                    _ColumnData[ColumnUsage.FreezeDriedData] = FreezeDryObjectIntoBase64String(fieldData);
                    _ColumnData[ColumnUsage.DataType] = fieldData.GetType().ToString();
                    _ColumnData[ColumnUsage.DisplayText] = columnDisplayText;
                    _ColumnData[ColumnUsage.Reserved] = string.Empty;
                }
            }
        }

        private void ParseValue(string fieldData, string columnDisplayText)
        {
            this.Dispose();
            if (string.IsNullOrEmpty(fieldData))
            {
                _ColumnData = new SPFieldMultiColumnValue(ColumnUsage.COLUMN_ARRAY_SIZE);
             }
            else
            {
                //If its formated as a column string it will be delimited with ";#"
                if (fieldData.StartsWith(ColumnUsage.COLUMN_DELIMITER))
                {
                    _ColumnData = new SPFieldMultiColumnValue(fieldData);
                    if (_ColumnData.Count != ColumnUsage.COLUMN_ARRAY_SIZE)
                    {
                        throw new InvalidCastException("Data is not freeze dried");
                    }
                }
                else
                {
                    //We are packing a simple string
                    _ColumnData = new SPFieldMultiColumnValue(ColumnUsage.COLUMN_ARRAY_SIZE);
                    _ColumnData[ColumnUsage.FreezeDriedData] = FreezeDryObjectIntoBase64String(fieldData);
                    _ColumnData[ColumnUsage.DataType] = fieldData.GetType().ToString();
                    _ColumnData[ColumnUsage.DisplayText] = columnDisplayText;
                    _ColumnData[ColumnUsage.Reserved] = string.Empty;
                }
            }

        }

        internal static string FreezeDryObjectIntoBase64String(object serializableObject)
        {
            string returnValue = string.Empty;
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            using (MemoryStream memoryStream = new MemoryStream())
            {
                binaryFormatter.Serialize(memoryStream, serializableObject);
                memoryStream.Position = 0;
                returnValue = Convert.ToBase64String(memoryStream.ToArray());
            }
            return returnValue;
        }

        internal static object HydrateObjectFromBase64String(string base64Data, ref Exception lastError)
        {
            lastError = null;
            object returnValue = null;
            try
            {
                if (base64Data.Length > 3)
                {
                    using (MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(base64Data)))
                    {
                        BinaryFormatter binaryFormatter = new BinaryFormatter();
                        memoryStream.Position = 0;
                        returnValue = binaryFormatter.Deserialize(memoryStream);
                    }
                }
            }
            catch (Exception rehydrationError)
            {
                returnValue = null;
                lastError = rehydrationError;
            }
            return returnValue;
        }

        public void Dispose()
        {
            _LastError = null;
            if (null != _RehydratedData)
            {
                IDisposable disposeIt = _RehydratedData as IDisposable;
                if (null != disposeIt) disposeIt.Dispose();
                _RehydratedData = null;
            }
        }
    }

}

namespace Version3.SharePoint.Generic
{

    [Guid("A7B6BB3E-8728-4a9d-A0E7-B7CAFD7FB8AF")]
    [CLSCompliant(false)]
    public class FreezeDriedDataFieldValue<T> : Version3.SharePoint.FreezeDriedDataFieldValue
    {
        public FreezeDriedDataFieldValue()
            : this(string.Empty)
        {
        }

        public FreezeDriedDataFieldValue(object fieldData)
            :base(fieldData)
        {
            
        }

        public FreezeDriedDataFieldValue(string fieldData)
            :base(fieldData)
        {
            
        }
        public FreezeDriedDataFieldValue(object fieldData, string columnDisplayText)
            :base(fieldData,columnDisplayText)
        {
            
        }

        public FreezeDriedDataFieldValue(string fieldData, string columnDisplayText)
            :base(fieldData,columnDisplayText)
        {
            
        }

        
        public new T Value
        {
            get
            {
                return (T)base.Value;
            }
            set
            {
                base.Value = value;
            }
        }
    }
}

